/* 

  VOTable parser


  Copyright © 2010-2011 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.


  Reference:

  http://www.ivoa.net/Documents/VOTable/20091130/REC-VOTable-1.2.html

*/

#include "votable.h"
#include <wx/wx.h>
#include <wx/xml/xml.h>
#include <wx/uri.h>
#include <wx/url.h>
#include <wx/filesys.h>
#include <wx/fs_inet.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/sstream.h>
#include <wx/file.h>
#include <fitsio.h>
#include <map>
#include <vector>
#include <algorithm>
#include <utility>

using namespace std;



VOInfoBasic::VOInfoBasic(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();

  wxXmlAttribute *prop = root->GetAttributes();
  while(prop) {
    params[prop->GetName()] = prop->GetValue();
    //    wxLogDebug(_("Field: ")+prop->GetName()+_("= ")+prop->GetValue());
    prop = prop->GetNext();
  }


  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    /*
    else if( node->GetName() == wxT("VALUE") )
      values.push_back(node->GetNodeContent());
    */

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    node = node->GetNext();
  }
}

wxString VOField::GetLabel() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "name" ) // ID ???
      return i->second;

  return wxEmptyString;
}

wxString VOField::GetType() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "datatype" )
      return i->second;

  return wxEmptyString;
}

wxString VOField::GetUnit() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "unit" )
      return i->second;

  return wxEmptyString;
}


VOGroup::VOGroup(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "PARAMref" )  // ????
      refs.push_back(node->GetNodeContent());

    node = node->GetNext();
  }  
}




VOTableData::VOTableData(): nrows(0) {}

VOTableData::VOTableData(const wxXmlNode *root): nrows(0)
{
  wxASSERT(root);

  wxXmlNode *tr = root->GetChildren();
  while (tr) {
    
    if( tr->GetName() == "TR" ) {
      
      nrows++;
      rows.push_back(tr);
      
      wxXmlNode *td = tr->GetChildren();
      while (td) {
	elements.push_back(td->GetNodeContent());
	td = td->GetNext();
      }

    }

    tr = tr->GetNext();
  }
}




VOData::VOData() {}

VOData::VOData(const wxXmlNode *root)
{
  wxASSERT(root);

  wxXmlNode *node = root->GetChildren();
  while (node) {
    
    if( node->GetName() == "TABLEDATA" ) {
      tablenode = node;
      table = VOTableData(node);
    }
    else
      wxLogDebug("Only TABLEDATA element is supported.");

    node = node->GetNext();
  }
}




VOTableTable::VOTableTable(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    //    wxLogDebug(node->GetName());

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "FIELD" )
      fields.push_back(VOField(node));

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    else if( node->GetName() == "DATA" )
      data = VOData(node);

    node = node->GetNext();
  }
}

int VOTableTable::RecordCount() const
{
  return data.table.nrows;
}

vector<wxString> VOTableTable::GetRecord(int n) const
{
  vector<wxString> r;
  int ncol = fields.size();

  //  wxLogDebug(_("%d ")+data.table.elements[1],ncol);

  for(int i = 0; i < ncol; i++)
    r.push_back(data.table.elements[n*ncol+i]);

  return r;
}

vector<wxString> VOTableTable::GetColumn(int n) const
{
  vector<wxString> c;
  int ncol = fields.size();

  //  wxLogDebug(_("%d ")+data.table.elements[1],ncol);

  for(int i = 0; i < data.table.nrows; i++)
    c.push_back(data.table.elements[i*ncol+n]);

  return c;
}



VOResource::VOResource(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    //    wxLogDebug(node->GetName());

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    else if( node->GetName() == "TABLE" )
      tables.push_back(VOTableTable(node));

    node = node->GetNext();
  }
}


VOTable::VOTable(const wxURL& url): wxXmlDocument(),index(0)
{
  canvas_size = 200.0;
  mag_limit = 15.0;
  proj_scale = 1000.0;

  wxFileSystem fs;
  fs.AddHandler(new wxInternetFSHandler);
  wxFSFile *f = fs.OpenFile(url.BuildURI());
  if( !f ) return;
  Load(*(f->GetStream()));
  delete f;
  if( IsOk() )
    Parse();
}


VOTable::VOTable(const wxXmlDocument& vt): wxXmlDocument(vt),index(0) 
{
  canvas_size = 200.0;
  mag_limit = 15.0;
  proj_scale = 1000.0;

  if( !( vt.IsOk() && vt.GetRoot()->GetName() == "VOTABLE" ) )
    return;
  Parse();
}

bool VOTable::Parse()
{
  description.Clear();
  params.clear();
  groups.clear();
  resources.clear();
  infos.clear();



  //wxASSERT(IsOk() && GetRoot()->GetName() == "VOTABLE");

  const wxXmlNode *node = GetRoot()->GetChildren();
  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "RESOURCE" )
      resources.push_back(VOResource(node));

    node = node->GetNext();
  }  

  wxASSERT(resources.size() > 0);
  //  isok = resources.size() > 0;
  return resources.size() > 0;
}

void VOTable::SetProjection(const wxString& a) { proj_type = a;}
void VOTable::SetProjectionCenter(double a,double d) 
{ 
  proj_alpha = a;
  proj_delta = d;
}
void VOTable::SetSize(double l) { canvas_size = l; }
void VOTable::SetScale(double c) { proj_scale = c; }
void VOTable::SetMaglim(double x) { mag_limit = x; }
void VOTable::SetMagkey(const wxString& a) { mag_key = a; }

wxString VOTable::GetDescription() const { return description; }
//bool VOTable::IsOk() const { return isok; }

int VOTable::RecordCount() const 
{
  wxASSERT(resources[index].tables.size() == 1);
  return resources[index].tables[0].RecordCount();
}

vector<wxString> VOTable::GetResources() const
{
  vector<wxString> fr;
  for(size_t i = 0; i < resources.size(); i++)
    fr.push_back(resources[i].description);
  return fr;
}

vector<wxString> VOTable::GetFields() const
{
  vector<wxString> fl;
  if( resources.empty() ) return fl;
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetLabel());
  return fl;
}

vector<wxString> VOTable::GetTypes() const
{
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  vector<wxString> fl;
  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetType());
  return fl;
}

vector<wxString> VOTable::GetUnits() const
{
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  vector<wxString> fl;
  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetUnit());
  return fl;
}

vector<wxString> VOTable::GetRecord(int i) const
{
  wxASSERT(resources[index].tables.size() == 1);
  VOTableTable t = resources[index].tables[0];
  return t.GetRecord(i);
}


vector<wxString> VOTable::GetColumn(int i) const
{
  wxASSERT(resources[index].tables.size() == 1);
  VOTableTable t = resources[index].tables[0];
  return t.GetColumn(i);
}

bool VOTable::Save(const wxString& filename, const wxString& type)
{
  wxASSERT(IsOk());

  if( type == "FITS" && ! filename.IsEmpty() )
    return SaveFITS(filename);

  //  if( filename.IsEmpty()

     
  wxFFile ffile;
  if( filename.IsEmpty() )
    ffile.Attach(stdout);
  else
    ffile.Open(filename.c_str(),"w");

  if( ! ffile.IsOpened() )
    return false;

  wxFFileOutputStream output(ffile);
      
  //  wxFFileOutputStream output(filename.IsEmpty() ? wxFile::fd_stdout : filename);
    
      //  wxFFileOutputStream o(filename);
  //  wxLogDebug("*****");

  return Save(output,type);
}

bool VOTable::Save(wxOutputStream& ostream, const wxString& type)
{

  if( type.IsEmpty() || type == "XML" )
    return wxXmlDocument::Save(ostream);

  else if( type == "CSV" )
    return SaveCSV(ostream);

  else if( type == "TXT" )
    return SaveText(ostream);

  else if( type == "SVG" )
    return SaveSVG(ostream);
    
  return false;
}


vector<wxString> VOTable::TypeTrafo(const vector<wxString>& type)
{
  vector<wxString> tform;
  for(vector<wxString>::const_iterator i = type.begin(); i != type.end(); ++i){

    if( *i == "char" )
      tform.push_back("A");
    else if( *i == "double" )
      tform.push_back("1D");
    else if( *i == "boolean" )
      tform.push_back("1L");
    else if( *i == "bit" )
      tform.push_back("1X");
    else if( *i == "unsignedByte" )
      tform.push_back("1B");
    else if( *i == "short" )
      tform.push_back("1I");
    else if( *i == "int" )
      tform.push_back("1J");
    else if( *i == "long" )
      tform.push_back("1K");
    else if( *i == "float" )
      tform.push_back("1E");
    else if( *i == "floatComplex" )
      tform.push_back("1C");
    else if( *i == "doubleComplex" )
      tform.push_back("1M");
    else if( *i == "unicodeChar" )
      tform.push_back("");
    else
      wxLogFatalError("Unsupported data type");

  }
  return tform;
}
  

char **VOTable::GetArray(const vector<wxString>& vec)
{
  int n = vec.size();
  char **x = new char*[n];
  for(int j = 0; j < n; j++) {
    size_t l = vec[j].Len();
    if( l > 0 ) {
      char *a = new char[l + 1];
      for(size_t i = 0; i < l; i++)
	a[i] = vec[j].GetChar(i);
      a[l] = '\0';
      x[j] = a;
    }
    else
      x[j] = 0;
  }
  return x;
}

bool VOTable::SaveText(wxOutputStream& output)
{
  if( ! IsOk() ) return false;
  /*
  wxFFile ffile;
  if( file.IsEmpty() )
    ffile.Attach(stdout);
  else
    ffile.Open(file.c_str(),_("w"));
  wxFFileOutputStream output(ffile);
  */
  wxTextOutputStream cout(output);
  
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( i != fs.begin() ) cout << "\t";
    wxString a(*i);
    a.Replace("\n",""); a.Replace("\r","");
    cout << a;
    //    cout << wxString(*i);
  }
  cout << endl;

  for(int n = 0; n < RecordCount(); n++) {

    vector<wxString> r = GetRecord(n);
    
    for(vector<wxString>::const_iterator i = r.begin(); i != r.end(); ++i) {
      if( i != r.begin() ) cout << " \t";
      //cout << *i;
      wxString a(*i);
      a.Replace("\n",""); a.Replace("\r","");
      cout << a;
    }
    cout << endl;
  }

  return true;
}

bool VOTable::SaveCSV(wxOutputStream& output)
{
  // http://en.wikipedia.org/wiki/Comma-separated_values

  if( ! IsOk() ) return false;

  //  wxFFileOutputStream output(file);
  wxTextOutputStream cout(output);
  
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( i != fs.begin() ) cout << ",";
    wxString a(*i);
    a.Replace("\n",""); a.Replace("\r","");
    cout << a;
  }
  cout << endl;

  for(int n = 0; n < RecordCount(); n++) {

    vector<wxString> r = GetRecord(n);
    
    for(vector<wxString>::const_iterator i = r.begin(); i != r.end(); ++i) {
      if( i != r.begin() ) cout << ",";
      wxString a(*i);
      a.Replace("\n",""); a.Replace("\r","");
      cout << a;
    }
    
    cout << endl;
  }

  return true;
}

bool VOTable::SaveFITS(const wxString& fitsname)
{
  if( ! IsOk() ) return false;

  vector<wxString> fr = GetResources();
  vector<wxString> fs = GetFields();
  vector<wxString> ft = GetTypes();
  vector<wxString> fu = GetUnits();

  wxASSERT(fr.size() > 0);
  wxASSERT(fs.size() == ft.size() && ft.size() == fu.size());

  char **ttype = GetArray(fs);
  char **tform = GetArray(TypeTrafo(ft));
  char **tunit = GetArray(fu);

  fitsfile *file;
  int status = 0;
  const char *ehdu = fr[0].fn_str();
  fits_create_file(&file, fitsname.fn_str(), &status);
  fits_create_tbl(file,BINARY_TBL,0,fs.size(),ttype,tform,tunit,
		  (char *) ehdu,&status);

  for(size_t j = 0; j < fs.size(); j++) {
    delete[] ttype[j];
    delete[] tform[j];
    delete[] tunit[j];
  }
  delete[] ttype;
  delete[] tform;
  delete[] tunit;

  for(size_t i = 0; i < fs.size(); i++) {

    vector<wxString> c = GetColumn(i);
    size_t n = c.size();

    if( ft[i] == "boolean" ) {
      char *x = new char[n];
      for(size_t j = 0; j < n; j++)
	x[j] = c[j]== "T" || c[j]== "t" || c[j]== "1" ? 'T' : 'F';
      fits_write_col(file,TLOGICAL,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "bit" ) {
      char *x = new char[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	c[j].ToLong(&t);
	if( ! (t == 0 || t == 1) ) 
	  wxLogFatalError("Bad bit-type value: "+fs[i]+" at %d",j);
	x[j] = t;
      }
      fits_write_col(file,TBIT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "unsignedByte" ) {
      unsigned char *x = new unsigned char[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	c[j].ToLong(&t);
	if( ! (0 <= t && t < 256) ) 
	  wxLogFatalError("Value out of range: "+fs[i]+" at %d",j);
	x[j] = t;
      }
      fits_write_col(file,TBYTE,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "short" ) {
      short *x = new short[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	c[j].ToLong(&t);
	//	if( ! c[j].ToLong(&t) ) 
	  //	  wxLogFatalError("Failed convert to short: "+fs[i]+" at %d",j);
	x[j] = t;
      }
      fits_write_col(file,TSHORT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "int" ) {
      int *x = new int[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	c[j].ToLong(&t);
	//	if( ! c[j].ToLong(&t) ) 
	//	  wxLogFatalError("Failed convert to int: "+c[j]+" at "+fs[i]+" %d",j);
	x[j] = t;
      }
      fits_write_col(file,TINT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "long" ) {
      long *x = new long[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	c[j].ToLong(&t);
	//	if( ! c[j].ToLong(&t) ) 
	//	  wxLogFatalError("Failed convert to long: "+c[j]+" at "+fs[i]+" %d",j);
	x[j] = t;
      }
      fits_write_col(file,TLONG,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "char" || ft[i] == "unicodeChar" ) {
      char **x = GetArray(c);
      fits_write_col(file,TSTRING,i+1,1,1,n,x,&status);    
      for(size_t j = 0; j < n; j++)
	delete[] x[j];
      delete[] x;
    }

    else if( ft[i] == "float" ) {
      float *x = new float[n];
      for(size_t j = 0; j < n; j++) {
	double t;
	c[j].ToDouble(&t);
	//	if( ! c[j].ToDouble(&t) ) 
	//	  wxLogFatalError("Failed convert to float: "+c[j]+" at "+fs[i]+" %d",j);
	x[j] = t;
      }
      fits_write_col(file,TFLOAT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "double" ) {
      double *x = new double[n];
      for(size_t j = 0; j < n; j++) {
	double t;
	c[j].ToDouble(&t);
	//	if( ! c[j].ToDouble(&t) )
	//	  wxLogFatalError("Failed convert to double: "+c[j]+" at "+fs[i]+" %d",j);
	x[j] = t;
      }
      fits_write_col(file,TDOUBLE,i+1,1,1,n,x,&status);

      delete[] x;
    }

    else if( ft[i] == "floatComplex" ) {
      float *x = new float[2*n];
      for(size_t j = 0; j < n; j++) {
	wxStringInputStream is(c[j]);
	wxTextInputStream ts(is);
	ts >> x[2*j] >> x[2*j+1];
      }
      fits_write_col(file,TCOMPLEX,i+1,1,1,2*n,x,&status);
      delete[] x;
    }
    else if( ft[i] == "doubleComplex" ) {
      double *x = new double[2*n];
      for(size_t j = 0; j < n; j++) {
	wxStringInputStream is(c[j]);
	wxTextInputStream ts(is);
	ts >> x[2*j] >> x[2*j+1];
      }
      fits_write_col(file,TDBLCOMPLEX,i+1,1,1,2*n,x,&status);
      delete[] x;
    }


  }
  if( fits_close_file(file, &status) )
    fits_report_error(stderr,status);

  return status == 0;
}

double VOTable::ToDouble(const wxString& a)
{
  double x;
  a.ToDouble(&x);
  return x;
}

bool VOTable::SaveSVG(wxOutputStream& output)
{
  int nra = -1, ndec = -1, nmag = -1;
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( *i == "RA" ) {
      nra = i - fs.begin();
    }
    else if( *i == "DEC" ) {
      ndec = i - fs.begin();
    }
    else if( *i == mag_key ) {
      nmag = i - fs.begin();
    }
  }
  
  if( nra == -1 || ndec == -1 || nmag == -1 )
    return false;

  vector<wxString> xra = GetColumn(nra);
  vector<wxString> xdec = GetColumn(ndec);
  vector<wxString> xmag = GetColumn(nmag);

  wxTextOutputStream cout(output);
  cout << "<svg xmlns=\"http://www.w3.org/2000/svg\">" << endl;

  // WARNINNG: only simple rectangular projection is implemented

  for(size_t i = 0; i < xra.size(); i++) {
    double ra = ToDouble(xra[i]);
    double dec = ToDouble(xdec[i]);
    double mag = ToDouble(xmag[i]);
    double x = canvas_size/2.0 - proj_scale*(ra - proj_alpha);
    double y = canvas_size - proj_scale*(dec - proj_delta);// reverse y-axis !!
    double r = 3.0*pow(10.0,0.11*(mag_limit - mag));

    cout << "<circle cx=\"" << x << "\" cy=\"" << y
	 << "\" r=\"" << r << "\"/>" << endl;
  }
  
  cout << "</svg>" << endl;
  return true;
}



// needs to add other types
bool cmp(pair<int,double> a, pair<int,double> b)
{
  //  wxLogDebug("%f %f",b.second,a.second);
  return b.second > a.second;
}

bool VOTable::Sort(const wxString& key)
{
  int n = -1;
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( *i == key ) {
      n = i - fs.begin();
      break;
    }
  }
  
  if( n == -1 )
    return false;

  vector< pair<int,double> > d;

  vector<wxString> col = GetColumn(n);

  for(vector<wxString>::const_iterator i = col.begin(); i != col.end(); ++i) {
    wxString a(*i);
    double x;
    a.ToDouble(&x);
    d.push_back(make_pair(i-col.begin(),x));
  }
  
  sort(d.begin(),d.end(),cmp);
  
  /*
  for(vector< pair<int,double> >::const_iterator i = d.begin(); i != d.end(); ++i) 
  wxLogDebug("%d %f",i->first,i->second);
  */

  wxASSERT(resources[index].tables.size() == 1);
  wxXmlNode *t = resources[index].tables[0].data.tablenode;
  wxXmlNode *nt = new wxXmlNode(t->GetParent(),t->GetType(),
				t->GetName(),t->GetContent(),
				t->GetAttributes(),t->GetNext(),
				t->GetLineNumber());
      
  vector<wxXmlNode *> rows(resources[index].tables[0].data.table.rows);

  for(vector< pair<int,double> >::const_iterator i=d.begin(); i!=d.end(); ++i)
    nt->AddChild(rows[i->first]);

  wxXmlNode *parent = t->GetParent();
  parent->RemoveChild(t);

  return Parse();
}
